package com.aliothservice.catfish.admin;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import com.aliothservice.catfish.device.CollectorInboundAdapter;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class MockupInboundAdapter extends ChannelInboundHandlerAdapter {
	
	public static final String REQUEST_TYPE_HEARTBEAT = "heartbeat";
	public static final String REQUEST_TYPE_LOCATION_REPORTING = "location";
	public static final String REQUEST_TYPE_TEMPERATURE_REPORTING = "temperature";
	public static final String REQUEST_TYPE_HUMIDITY_REPORTING = "humidity";
	
	private ByteBuf headerBuffer;
	private ByteBuf bodyBuffer;
	
	private boolean hasHeaderRead = false;
	
	@Override
	public void handlerAdded(ChannelHandlerContext context) {
		// allocate header buffer
		headerBuffer = context.alloc().buffer(8);
	}
	
	@Override
	public void handlerRemoved(ChannelHandlerContext context) {
		headerBuffer.release();
		headerBuffer = null;
		if (null != bodyBuffer) {
			bodyBuffer.release();
			bodyBuffer = null;
		}
	}
	
	@Override
    public void channelActive(ChannelHandlerContext context) {
		if (0 == this.getReqType().compareTo(REQUEST_TYPE_HEARTBEAT)) {
			final ByteBuf reqBuffer = context.alloc().buffer(16);
			reqBuffer.writeBytes(this.getDeviceSN().getBytes());
			reqBuffer.writeInt(0);
			reqBuffer.writeShort(CollectorInboundAdapter.CMD_HEARTBEAT);
			reqBuffer.writeShort(0);
			context.writeAndFlush(reqBuffer);
			String reqDump = "REQUEST_HEADER {SN: " + this.getDeviceSN() +", Body Length: 0, Command: 0 (heartbeat), Reserved: 0}";
			redisTemplate.opsForValue().set(this.getReceipt()+"_outbound", reqDump, 5, TimeUnit.MINUTES);
			logger.debug("request header set, key:" + this.getReceipt()+"_outbound" + " value: " + reqDump);
		} else if (0 == this.getReqType().compareTo(REQUEST_TYPE_TEMPERATURE_REPORTING)) {
			final ByteBuf reqBuffer = context.alloc().buffer(16 /* header */ + 28 /* body */);
			reqBuffer.writeBytes(this.getDeviceSN().getBytes());
			reqBuffer.writeInt(28);
			reqBuffer.writeShort(CollectorInboundAdapter.CMD_REPORTING_TEMPERATURE);
			reqBuffer.writeShort(0);
			reqBuffer.writeInt(this.getTemperature().intValue());
			reqBuffer.writeDouble(this.getLongitude().doubleValue());
			reqBuffer.writeDouble(this.getLatitude().doubleValue());
			Date now = new Date();
			reqBuffer.writeLong(now.getTime());
			context.writeAndFlush(reqBuffer);
			String reqDump = "REQUEST_HEADER {sn: " + this.getDeviceSN() + ", Body Length: 28, Command: 2 (temperature reporting), Position: 0}";
			reqDump += "\n";
			reqDump += "REQUEST_BODY {Temperature: " + this.getTemperature() + ", Longitude: " + this.getLongitude() + ", Latitude: " + this.getLatitude() + ", Timestamp: " + now.getTime() + "}";
			redisTemplate.opsForValue().set(this.getReceipt()+"_outbound", reqDump, 5, TimeUnit.MINUTES);
		} else if (0 == this.getReqType().compareTo(REQUEST_TYPE_HUMIDITY_REPORTING)) {
			final ByteBuf reqBuffer = context.alloc().buffer(16 /* header */ + 28 /* body */);
			reqBuffer.writeBytes(this.getDeviceSN().getBytes());
			reqBuffer.writeInt(28);
			reqBuffer.writeShort(CollectorInboundAdapter.CMD_REPORTING_HUMIDITY);
			reqBuffer.writeShort(0);
			reqBuffer.writeInt(this.getHumidity().intValue());
			reqBuffer.writeDouble(this.getLongitude().doubleValue());
			reqBuffer.writeDouble(this.getLatitude().doubleValue());
			Date now = new Date();
			reqBuffer.writeLong(now.getTime());
			context.writeAndFlush(reqBuffer);
			String reqDump = "REQUEST_HEADER {sn: " + this.getDeviceSN() + ", Body Length: 28, Command: 3 (humidity reporting), reserved: 0}";
			reqDump += "\n";
			reqDump += "REQUEST_BODY {Humidity: " + this.getHumidity() + ", Longitude: " + this.getLongitude() + ", Latitude: " + this.getLatitude() + ", Timestamp: " + now.getTime() + "}";
			redisTemplate.opsForValue().set(this.getReceipt()+"_outbound", reqDump, 5, TimeUnit.MINUTES);
		} else if (0 == this.getReqType().compareTo(REQUEST_TYPE_LOCATION_REPORTING)) {
			final ByteBuf reqBuffer = context.alloc().buffer(16 /* header */ + 24 /* body */);
			reqBuffer.writeBytes(this.getDeviceSN().getBytes());
			reqBuffer.writeInt(24);
			reqBuffer.writeShort(CollectorInboundAdapter.CMD_REPORTING_LOCATION);
			reqBuffer.writeShort(0);
			reqBuffer.writeDouble(this.getLongitude().doubleValue());
			reqBuffer.writeDouble(this.getLatitude().doubleValue());
			Date now = new Date();
			reqBuffer.writeLong(now.getTime());
			context.writeAndFlush(reqBuffer);
			String reqDump = "REQUEST_HEADER {sn: " + this.getDeviceSN() + ", Body Length: 24, Command: 1 (location reporting), reserved: 0}";
			reqDump += "\n";
			reqDump += "REQUEST_BODY {Longitude: " + this.getLongitude() + ", Latitude: " + this.getLatitude() + ", Timestamp: " + now.getTime() + "}";
			redisTemplate.opsForValue().set(this.getReceipt()+"_outbound", reqDump, 5, TimeUnit.MINUTES);
		}
	}
	
	@Override
	public void channelRead(ChannelHandlerContext context, Object message) {
		ByteBuf buffer = (ByteBuf)message;
		int responseCommand = -1;
		int responseStatus = -1;
		String command = "";
		String status = "";
		if (!hasHeaderRead) {
			logger.debug("reading response header...");
			if (buffer.readableBytes() < 8) {
				return;
			}
			logger.debug("response header has been read out");
			headerBuffer.writeBytes(buffer, 8);
			hasHeaderRead = true;
			responseCommand = headerBuffer.readShort();
			if (responseCommand == 0) {
				command = "keep going";
			} else if (responseCommand == 1) {
				command = "start recording";
			} else if (responseCommand == 2) {
				command = "stop recording";
			}
			responseStatus = headerBuffer.readShort();
			if (responseStatus == 0) {
				status = "accepted";
			} else {
				status = "not processed";
			}
			bodyLength = headerBuffer.readInt();
			resHeaderDump = "RESPONSE_HEADER {Command: " + responseCommand + "(" + command + "), Status: " + responseStatus + "(" + status + "), Body Length: " + bodyLength + "}";
			if (bodyLength > 0) {
				bodyBuffer = context.alloc().buffer(bodyLength);
				if (buffer.readableBytes() >= bodyLength) {
					bodyBuffer.writeBytes(buffer, bodyLength);
					if (0 == this.getReqType().compareTo(REQUEST_TYPE_HEARTBEAT)) {
						int temperatureMin = bodyBuffer.readInt();
						int temperatureMax = bodyBuffer.readInt();
						resHeaderDump += "\n";
						resHeaderDump += "RESPONSE_BODY {Temperature Low: " + temperatureMin + ", Temperature High: " + temperatureMax + "}";
						redisTemplate.opsForValue().set(this.getReceipt()+"_inbound", resHeaderDump, 5, TimeUnit.MINUTES);
						logger.debug("response header set, key:" + this.getReceipt()+"_inbound" + " value: " + resHeaderDump);
						buffer.release();
						buffer = null;
					}
				}
			} else {
				redisTemplate.opsForValue().set(this.getReceipt()+"_inbound", resHeaderDump, 5, TimeUnit.MINUTES);
				logger.debug("response header set, key:" + this.getReceipt()+"_inbound" + " value: " + resHeaderDump);
				buffer.release();
				buffer = null;
			}
		} else {
			logger.debug("reading response body");
			if (buffer.readableBytes() >= bodyLength) {
				logger.debug("body has been read");
				bodyBuffer.writeBytes(buffer, bodyLength);
				if (0 == this.getReqType().compareTo(REQUEST_TYPE_HEARTBEAT)) {
					if (bodyLength > 0) {
						int temperatureMin = bodyBuffer.readInt();
						int temperatureMax = bodyBuffer.readInt();
						resHeaderDump += "\n";
						resHeaderDump += "RESPONSE_BODY {Temperature Low: " + temperatureMin + ", Temperature High: " + temperatureMax + "}";
						redisTemplate.opsForValue().set(this.getReceipt()+"_inbound", resHeaderDump, 5, TimeUnit.MINUTES);
						logger.debug("response header set, key:" + this.getReceipt()+"_inbound" + " value: " + resHeaderDump);
					} else {
						redisTemplate.opsForValue().set(this.getReceipt()+"_inbound", resHeaderDump, 5, TimeUnit.MINUTES);
						logger.debug("response header set, key:" + this.getReceipt()+"_inbound" + " value: " + resHeaderDump);
					}
				}
				buffer.release();
				buffer = null;
			}
		}
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
		context.close();
	}
	
	public String getReceipt() {
		return receipt;
	}

	public void setReceipt(String receipt) {
		this.receipt = receipt;
	}

	public RedisTemplate<String, Object> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public String getReqType() {
		return reqType;
	}

	public void setReqType(String reqType) {
		this.reqType = reqType;
	}

	public String getDeviceSN() {
		return deviceSN;
	}

	public void setDeviceSN(String deviceSN) {
		this.deviceSN = deviceSN;
	}

	public Integer getTemperature() {
		return temperature;
	}

	public void setTemperature(Integer temperature) {
		this.temperature = temperature;
	}

	public Double getLongitude() {
		return longitude;
	}

	public void setLongitude(Double longitude) {
		this.longitude = longitude;
	}

	public Double getLatitude() {
		return latitude;
	}

	public void setLatitude(Double latitude) {
		this.latitude = latitude;
	}

	public Integer getHumidity() {
		return humidity;
	}

	public void setHumidity(Integer humidity) {
		this.humidity = humidity;
	}

	private String deviceSN;
	private String reqType;
	private String receipt;
	
	private Integer temperature;
	private Integer humidity;
	private Double longitude;
	private Double latitude;
	
	private int bodyLength = -1;
	private String resHeaderDump = "";
	
	private RedisTemplate<String, Object> redisTemplate;
	
	private static final Logger logger = LoggerFactory.getLogger(MockupInboundAdapter.class);
	
}
